﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection.Emit;
using System.Text;
using System.Threading.Tasks;
using Tippelde.Entities;
using Tippelde.Services.Dtos;
using Tippelde.Services.Exceptions;

namespace Tippelde.Services.Bets
{
    public class BetManager : IBetManager
    {
        private readonly System.Linq.Expressions.Expression<Func<Bet, BetHeaderData>> BetHeaderSelector =
             b => new BetHeaderData
             {
                 Id = b.Id,
                 HomeBet = b.HomeBet,
                 AwayBet = b.AwayBet,
                 Checked = b.Checked
             };


        public BetHeaderData GetBet(string userName, int eventId)
        {
            using (var ctx = new TippeldeEntities())
            {
                return ctx.Bets.Where(b => b.Event.Id == eventId && b.User.UserName == userName).Select(BetHeaderSelector).SingleOrDefault();
            }
        }

        public BetHeaderData AddBet(string userName, int eventId, int homeBet, int awayBet)
        {
            using (var ctx = new TippeldeEntities())
            {
                if (homeBet < 0 || awayBet < 0)
                    throw new TippeldeException("Nem Lehet a tipp negatív szám!");
                DateTime betTime = DateTime.Now;
                Event e = ctx.Events.Single(_e => _e.Id == eventId);
                if (betTime.CompareTo(e.BetStartAt) < 0 || betTime.CompareTo(e.BetEndAt) > 0 || betTime.CompareTo(e.EventDate) > 0)
                    throw new TippeldeException("Nincs az eseményre fogadási időszak!");
                User user = ctx.Users.Single(u => u.UserName == userName);
                Bet bet = new Bet
                {
                    User = user,
                    HomeBet = homeBet,
                    AwayBet = awayBet,
                    BetAt = betTime,
                    BetResult = null,
                    Checked = false,
                    Event = e,
                };
                user.Bets.Add(bet);
                e.Bets.Add(bet);
                ctx.Bets.Add(bet);
                ctx.SaveChanges();
                var Selector = BetHeaderSelector.Compile();
                return Selector(bet);
            }
        }


        public void DeleteBet(string userName, int eventId)
        {
            using (var ctx = new TippeldeEntities())
            {
                Bet bet = ctx.Bets.Single(b => b.Event.Id == eventId && b.User.UserName == userName);
                if (bet.Checked || bet.BetResult != null)
                    throw new TippeldeException("Ellenőrzött tipp nem törölhető!");
                bet.Event.Bets.Remove(bet);
                bet.User.Bets.Remove(bet);
                ctx.Bets.Remove(bet);
                ctx.SaveChanges();
            }
        }

        public void AddResultAndCheckBets(string userName, int eventId, ResultHeaderData res)
        {
            using (var ctx = new TippeldeEntities())
            {
                User editor = ctx.Users.Single(u => u.UserName == userName);
                Event e = ctx.Events.Single(e_ => e_.Id == eventId);
                if (e.Result != null)
                    throw new TippeldeException("Már van eredmény hozzáadva!");
                DateTime now = DateTime.Now;
                Result result = new Result
                {
                    HomeResult = res.HomeResult,
                    AwayResult = res.AwayResult,
                    AddedAt = now,
                    Event = e
                };
                e.Result = result;
                foreach (var bet in e.Bets)
                {
                    if (bet.Checked)
                        throw new TippeldeException("Már van tipp, ami ellenőrizve lett!");
                    BetResultStatus betStatus = CheckBet(bet, result);
                    BetResult betResult = new BetResult
                    {
                        CheckedAt = now,
                        Event = e,
                        PointsEarned = (int)betStatus,
                        User = bet.User,
                        Bet = bet,
                        CheckedBy = editor
                    };

                    bet.User.BetResults.Add(betResult);
                    bet.BetResult = betResult;
                    e.BetResults.Add(betResult);
                    ctx.BetResults.Add(betResult);
                    bet.Checked = true;
                }
                ctx.SaveChanges();
            }
        }

        public void DeleteResultAndRevertBetResults(string userName, int eventId)
        {
            using (var ctx = new TippeldeEntities())
            {
                User editor = ctx.Users.Single(u => u.UserName == userName);
                Event e = ctx.Events.Single(e_ => e_.Id == eventId);
                if (e.Result == null)
                    throw new TippeldeException("Nincs mit törölni!");
                foreach (var betResult in new List<BetResult>(e.BetResults))
                {
                    betResult.Bet.Checked = false;
                    betResult.Bet.BetResult = null;
                    betResult.User.BetResults.Remove(betResult);
                    ctx.BetResults.Remove(betResult);
                }
                e.BetResults.Clear();
                e.Result = null;
                ctx.SaveChanges();
            }
        }

        public BetResultStatus CheckBet(Bet b, Result r)
        {
            if (b.Checked)
                throw new TippeldeException("Már van tipp, ami ellenőrizve lett!");
            if (b.HomeBet == r.HomeResult && b.AwayBet == r.AwayResult)
                return BetResultStatus.SuccessComplete;
            int betDiff = Math.Sign(b.HomeBet - b.AwayBet);
            int resDiff = Math.Sign(r.HomeResult - r.AwayResult);
            return betDiff == resDiff ? BetResultStatus.SuccessPartial : BetResultStatus.Failed;
        }


        public CompetitionTable GetCompetitionTable(string userName, int groupId)
        {
            using (var ctx = new TippeldeEntities())
            {
                var competitionTable = new CompetitionTable();
                try
                {
                    var group = ctx.UserGroups.Where(ug => ug.GroupId == groupId && ug.User.UserName == userName).Select(ug => ug.Group).Single();

                    competitionTable.Group = new GroupHeaderData
                    {
                        Id = groupId,
                        Name = group.Name,
                        ShortName = group.ShortName
                    };

                    var bets = ctx.UserGroups.Where(ug => ug.GroupId == groupId && ug.User.UserName == userName).SelectMany(ug => ug.Group.Events).SelectMany(e => e.Bets).Where(b => b.BetResult != null);
                    var usersDict = group.UserGroups.Where(ug=>ug.GroupId==groupId).ToDictionary(ug => ug.UserId, ug => new int[] { 0, 0, 0, 0 });
                    if (bets.Count() > 0)
                    {
                        foreach (var item in usersDict)
                        {
                            var betsExists = bets.Where(d => d.User.Id == item.Key).Count() > 0;
                            
                            item.Value[0] = betsExists ? bets.Where(d => d.User.Id == item.Key).Sum(k => k.BetResult.PointsEarned) : 0;
                            item.Value[1] = betsExists ? bets.Where(d => d.User.Id == item.Key).Count(k => k.BetResult.PointsEarned == (int)BetResultStatus.Failed) : 0;
                            item.Value[2] = betsExists ? bets.Where(d => d.User.Id == item.Key).Count(k => k.BetResult.PointsEarned == (int)BetResultStatus.SuccessPartial) : 0;
                            item.Value[3] = betsExists ? bets.Where(d => d.User.Id == item.Key).Count(k => k.BetResult.PointsEarned == (int)BetResultStatus.SuccessPartial) : 0;
                        }
                    }
                    competitionTable.Result = usersDict.Select(kvp =>
                    new CompetititonTableRow
                    {
                        User = ctx.Users.Where(u=> u.Id == kvp.Key).Select(u=>new UserHeaderData{
                                UserId = u.Id,
                                FullName = u.FullName,
                                UserName = u.UserName,
                                Email = u.Email
                            }).SingleOrDefault(),
                        Points = kvp.Value[0],
                        FailedCount = kvp.Value[1],
                        SuccessPartialCount = kvp.Value[2],
                        SuccessCompleteCount = kvp.Value[3]
                    }).OrderByDescending(c =>c.Points).ToList();
                }
                catch (Exception ex)
                {
                    throw new TippeldeException("Nem elérhető a táblázat!");
                }
                return competitionTable;
            }
        }
    }
}
